#include "PSPGraphics.h"
#include "TgaLoader.h"

#include <pspdisplay.h>
#include <pspgu.h>
#include <pspge.h>
#include <malloc.h>

#define SCR_WIDTH 480
#define SCR_HEIGHT 272
#define BUF_WIDTH 512

namespace GenBen {
	
struct Vertex
{
	uint16 u,v;
	uint color;
	uint16 x,y,z;
	uint16 padd;
};

static uint __attribute__((aligned(16))) list[262144];
		
static uint staticOffset = 0;

static uint getMemorySize(uint width, uint height, uint psm)
{
	switch (psm)
	{
		case GU_PSM_T4:
			return (width * height) >> 1;

		case GU_PSM_T8:
			return width * height;

		case GU_PSM_5650:
		case GU_PSM_5551:
		case GU_PSM_4444:
		case GU_PSM_T16:
			return 2 * width * height;

		case GU_PSM_8888:
		case GU_PSM_T32:
			return 4 * width * height;

		default:
			return 0;
	}
}

void* getStaticVramBuffer(uint width, uint height, uint psm)
{
	uint memSize = getMemorySize(width,height,psm);
	void* result = (void*)staticOffset;
	staticOffset += memSize;
	return result;
}

void* getStaticVramTexture(uint width, uint height, uint psm)
{
	void* result = getStaticVramBuffer(width,height,psm);
	return (void*)(((uint)result) + ((uint)sceGeEdramGetAddr()));
}

PSPTexture::PSPTexture(const string& filename)
{
    void* data;
    loadTga(filename, width, height, &data);
	
	this->data = memalign(16, width * height * 4);
	memcpy(this->data, data, width * height * 4);

    free(data);
}

PSPTexture::~PSPTexture()
{
	if(data) free(data);
	data = NULL;
}

PSPGraphics::PSPGraphics()
{
	void* fbp0 = getStaticVramBuffer(BUF_WIDTH,SCR_HEIGHT,GU_PSM_8888);
	void* fbp1 = getStaticVramBuffer(BUF_WIDTH,SCR_HEIGHT,GU_PSM_8888);
	void* zbp = getStaticVramBuffer(BUF_WIDTH,SCR_HEIGHT,GU_PSM_4444);
	
	sceGuInit();
	sceGuStart(GU_DIRECT,list);
	sceGuDrawBuffer(GU_PSM_8888,fbp0,BUF_WIDTH);
	sceGuDispBuffer(SCR_WIDTH,SCR_HEIGHT,fbp1,BUF_WIDTH);
	sceGuDepthBuffer(zbp,BUF_WIDTH);
	sceGuOffset(2048 - (SCR_WIDTH/2),2048 - (SCR_HEIGHT/2));
	sceGuViewport(2048,2048,SCR_WIDTH,SCR_HEIGHT);
	sceGuDepthRange(0xc350,0x2710);
	sceGuScissor(0,0,SCR_WIDTH,SCR_HEIGHT);
	sceGuEnable(GU_SCISSOR_TEST);
	sceGuFrontFace(GU_CW);
	sceGuEnable(GU_TEXTURE_2D);
	sceGuEnable(GU_BLEND);
	sceGuClear(GU_COLOR_BUFFER_BIT|GU_DEPTH_BUFFER_BIT);
	sceGuFinish();
	sceGuSync(0,0);

	sceDisplayWaitVblankStart();
	sceGuDisplay(GU_TRUE);

	PSPRect dummy;
	rects.assign(MAX_QUADS, dummy);
	rect_count = 0;
	
	clear_flag = false;
}

PSPGraphics::~PSPGraphics()
{
	sceGuTerm();
}

Texture* PSPGraphics::loadTexture(string filename)
{
	return (Texture*)new PSPTexture(filename);
}

string PSPGraphics::getDescription()
{
	return string("PSPGU");
}

void PSPGraphics::clear(const Color& color)
{
	sceGuClearColor(GU_COLOR(color.r, color.g, color.b, color.a));
	sceGuClear(GU_COLOR_BUFFER_BIT);
	clear_flag = true;
}

void PSPGraphics::present()
{
	std::sort(rects.begin(), rects.begin() + rect_count);

	Vertex* vertices = (Vertex*)sceGuGetMemory(2 * rect_count * sizeof(Vertex));

	PSPTexture* active_tex = NULL;

	sceGuStart(GU_DIRECT,list);
	
	if(clear_flag)
	{
		sceGuClear(GU_COLOR_BUFFER_BIT);
		clear_flag = false;
	}
	
	// Fill vertex buffer
	for(uint i = 0; i < rect_count; ++i)
	{
		vertices[i*2].u = rects[i].source.min.x;
		vertices[i*2].v = rects[i].source.max.y;
		vertices[i*2].color = rects[i].tint;
		vertices[i*2].x = rects[i].dest.min.x;
		vertices[i*2].y = rects[i].dest.min.y;
		vertices[i*2].z = 0;

		vertices[i*2+1].u = rects[i].source.max.x;
		vertices[i*2+1].v = rects[i].source.min.y;
		vertices[i*2+1].color = rects[i].tint;
		vertices[i*2+1].x = rects[i].dest.max.x;
		vertices[i*2+1].y = rects[i].dest.max.y;
		vertices[i*2+1].z = 0;
	}

	// Draw quads
	
	sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0);
	
	uint rects_to_draw = 0;
	uint i;
	for(i = 0; i < rect_count; ++i)
	{
		rects_to_draw++;

		if(active_tex != rects[i].tex)
		{
			if(rects_to_draw > 0)
			{
				sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT|GU_COLOR_8888|GU_VERTEX_16BIT|GU_TRANSFORM_2D,
					rects_to_draw*2, 0, &vertices[(i-rects_to_draw)*2]);
				rects_to_draw = 0;
			}
			
			PSPRect rect = rects[i];
			active_tex = rect.tex;
			sceGuTexMode(GU_PSM_8888, 0, 0, 0); 
			sceGuTexImage(0, active_tex->getWidth(), active_tex->getHeight(),
				active_tex->getWidth(), active_tex->getData());
			sceGuTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA);
			sceGuTexFilter(GU_NEAREST, GU_NEAREST);
			sceGuTexScale(1.0f,1.0f);
			sceGuTexOffset(0.0f,0.0f);
			sceGuAmbientColor(0xffffffff);			
		}		
	}
	rects_to_draw++;
	if(rects_to_draw > 0)
	{
		sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT|GU_COLOR_8888|GU_VERTEX_16BIT|GU_TRANSFORM_2D,
			rects_to_draw*2, 0, &vertices[(i-rects_to_draw)*2]);
	}
	
	sceGuFinish();
	sceGuSync(0,0);

	sceDisplayWaitVblankStart();
	sceGuSwapBuffers();
	
	rect_count = 0;
}

void PSPGraphics::draw(Texture* texture, int layer, const RectI& source, const VectorI& dest, const Color& tint)
{
	draw(texture, layer, source, RectI(dest.x, dest.y, 0, 0), tint);
}

void PSPGraphics::draw(Texture* texture, int layer, const RectI& source, const RectI& dest, const Color& tint)
{
	bool full_source = source.max.x == 0 && source.max.y == 0;
	bool full_dest = dest.max.x == 0 && dest.max.y == 0;

	PSPTexture* tex = static_cast<PSPTexture*>(texture);
	
	PSPRect new_rect;
	new_rect.tint = GU_COLOR(tint.r, tint.g, tint.b, tint.a);
	new_rect.tex = tex;
	new_rect.layer = layer;
	new_rect.dest = dest;
	new_rect.source = source;

	if(full_source)
	{
		new_rect.source.max.x = texture->getWidth() - new_rect.source.min.x;
		new_rect.source.max.y = texture->getHeight() - new_rect.source.min.y;
	}

	uint temp_y = new_rect.source.min.y;
    new_rect.source.min.y = texture->getHeight() - new_rect.source.max.y;
	new_rect.source.max.y = texture->getHeight() - temp_y;

	if(full_dest)
	{
		int real_width = new_rect.source.max.x - new_rect.source.min.x;
		int real_height = new_rect.source.max.y - new_rect.source.min.y;
		new_rect.dest.max.x = new_rect.dest.min.x + real_width;
		new_rect.dest.max.y = new_rect.dest.min.y + real_height;
	}
	
	//temp_y = new_rect.dest.min.y;
    //new_rect.dest.min.y = new_rect.dest.max.y;
	//new_rect.dest.max.y = temp_y;

	rects[rect_count++] = new_rect;

	if(rect_count >= MAX_QUADS)
		criticalError("Too many rects!");
}

void PSPGraphics::draw(Texture* texture, int layer, const RectI& source, const VectorI& dest)
{
	draw(texture, layer, source, RectI(dest.x, dest.y, 0, 0), Color(1.0f, 1.0f, 1.0f));
}

void PSPGraphics::draw(Texture* texture, int layer, const RectI& source, const RectI& dest)
{
	draw(texture, layer, source, dest, Color(1.0f, 1.0f, 1.0f));
}

} // namespace
